/*-------------------------------------------------------------------------
 *
 * sprompt.c
 *	  simple_prompt()例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/sprompt.c
 *
 *-------------------------------------------------------------------------
 */
#include "c.h"

#include "common/fe_memutils.h"
#include "common/string.h"

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif


/*
 * simple_prompt
 *
 * 通用函数，特别用于交互式读取用户名和
 * 密码。 从 /dev/tty 或 stdin/stderr 读取。
 *
 * prompt:		要打印的提示，或 NULL 如果没有（自动本地化）
 * echo:		如果要隐藏输入的内容（用于密码），则设置为 false
 *
 * 输入（不带换行符）将作为一个分配的字符串返回。
 * 调用者负责在使用完后释放它。
 */
char * simple_prompt(const char *fc_prompt, bool fc_echo)
{
	return simple_prompt_extended(fc_prompt, fc_echo, NULL);
}

/*
 * simple_prompt_extended
 *
 * 这与 simple_prompt() 相同，除了可以选择提供 prompt_ctx，允许
 * 通过现有的 SIGINT 信号处理程序取消此函数，该处理程序将 longjmp
 * 到指定位置，仅在 *(prompt_ctx->enabled) 为 true 时。
 * 如果被取消，此函数返回空字符串，并且 prompt_ctx->canceled 被设置为 true。
 */
char * simple_prompt_extended(const char *fc_prompt, bool fc_echo,
					   PromptInterruptContext *fc_prompt_ctx)
{
	char	   *fc_result;
	FILE	   *fc_termin,
			   *fc_termout;
#if defined(HAVE_TERMIOS_H)
	struct termios fc_t_orig,
				fc_t;
#elif defined(WIN32)
	HANDLE		fc_t = NULL;
	DWORD		fc_t_orig = 0;
#endif

#ifdef WIN32

	/*
	 * Windows 控制台有一个“输入代码页”和一个“输出代码页”；
	 * 这些通常是相互匹配的，但它们很少与系统启动时定义的“Windows ANSI
	 * 代码页”匹配，并且此“char *”参数期望传递给 Windows API 函数。
	 * 微软 CRT write() 实现会在向控制台写入时自动在这些代码页之间转换文本。
	 * 为了识别此类文件描述符，它会在底层 HANDLE 上调用 GetConsoleMode(),
	 * 这反过来需要对 HANDLE 的 GENERIC_READ 访问权限。
	 * 以“w+”模式打开 termout 允许检测成功。
	 * 否则，write() 无法将描述符识别为控制台，非 ASCII 字符将显示不正确。
	 *
	 * XXX fgets() 仍然以控制台的输入代码页接收文本。
	 * 这使得非 ASCII 凭证不便携。
	 *
	 * 不直观的是，我们也以“w+”模式打开 termin，尽管我们只是读取它；
	 * 这是 SetConsoleMode() 成功所需的。
	 */
	fc_termin = fopen("CONIN$", "w+");
	fc_termout = fopen("CONOUT$", "w+");
#else

	/*
	 * 不要试图将这些合并为一个“w+”模式文件。这样在某些平台上不工作
	 * （例如 HPUX 10.20）。
	 */
	fc_termin = fopen("/dev/tty", "r");
	fc_termout = fopen("/dev/tty", "w");
#endif
	if (!fc_termin || !fc_termout
#ifdef WIN32

	/*
	 * 从 MSYS 1.0.10 控制台进行直接控制台 I/O 不可行。写入
	 * 无法到达用户可见之处；读取无限期阻塞。XXX 这影响
	 * 大多数 Windows 终端环境，包括 rxvt、mintty、Cygwin
	 * xterm、Cygwin sshd 和 PowerShell ISE。切换到更通用的测试。
	 */
		|| (getenv("OSTYPE") && strcmp(getenv("OSTYPE"), "msys") == 0)
#endif
		)
	{
		if (fc_termin)
			fclose(fc_termin);
		if (fc_termout)
			fclose(fc_termout);
		fc_termin = stdin;
		fc_termout = stderr;
	}

	if (!fc_echo)
	{
#if defined(HAVE_TERMIOS_H)
		/* 通过 tcgetattr/tcsetattr 禁用回显 */
		tcgetattr(fileno(fc_termin), &fc_t);
		fc_t_orig = fc_t;
		fc_t.c_lflag &= ~ECHO;
		tcsetattr(fileno(fc_termin), TCSAFLUSH, &fc_t);
#elif defined(WIN32)
		/* 需要文件的 HANDLE 来关闭回显 */
		fc_t = (HANDLE) _get_osfhandle(_fileno(termin));

		/* 首先保存旧配置 */
		GetConsoleMode(fc_t, &fc_t_orig);

		/* 设置为新模式 */
		SetConsoleMode(fc_t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
#endif
	}

	if (fc_prompt)
	{
		fputs(_(fc_prompt), fc_termout);
		fflush(fc_termout);
	}

	fc_result = pg_get_line(fc_termin, fc_prompt_ctx);

	/* 如果我们未能读取任何内容，则只返回一个空字符串 */
	if (fc_result == NULL)
		fc_result = pg_strdup("");

	/* 去掉尾部换行，包括 \r 以防我们在 Windows 上 */
	(void) pg_strip_crlf(fc_result);

	if (!fc_echo)
	{
		/* 恢复先前的回显行为，然后回显 \n */
#if defined(HAVE_TERMIOS_H)
		tcsetattr(fileno(fc_termin), TCSAFLUSH, &fc_t_orig);
		fputs("\n", fc_termout);
		fflush(fc_termout);
#elif defined(WIN32)
		SetConsoleMode(fc_t, fc_t_orig);
		fputs("\n", fc_termout);
		fflush(fc_termout);
#endif
	}
	else if (fc_prompt_ctx && fc_prompt_ctx->canceled)
	{
		/* 如果提示被取消，则也回显 \n */
		fputs("\n", fc_termout);
		fflush(fc_termout);
	}

	if (fc_termin != stdin)
	{
		fclose(fc_termin);
		fclose(fc_termout);
	}

	return fc_result;
}
